import * as THREE from "three";
import * as _ from 'lodash';
import * as materials from './materials';
const extrudePolyline = require('extrude-polyline');
const Complex = require('three-simplicial-complex')(THREE);

import { parseShape, forceArray, makeLookup } from './utils'
import { getTransforms } from './coords'
import TrafficLights from './traffic-lights'
import Vehicle from './vehicle'

import { OrbitControls } from "./OrbitControls.js";
const CUBE_DIR = 'static/sky/';
const CUBE_FILES = [  // 场景背景图片
  'TropicalSunnyDayLeft2048.png',
  'TropicalSunnyDayRight2048.png',
  'TropicalSunnyDayUp2048.png',
  'TropicalSunnyDayDown2048.png',
  'TropicalSunnyDayFront2048.png',
  'TropicalSunnyDayBack2048.png',
];
const FOG_RATE = 0.0005;

const VehicleClass = {
  PASSENGER: 'passenger', // 乘客
  RAIL_ELECTRIC: 'rail_electric', // 铁路-电
  RAIL: 'rail', // 铁路
  RAIL_URBAN: 'rail_urban',  // 铁路-城市
  TRAM: 'tram',  // 有轨电车
  PEDESTRIAN: 'pedestrian',  // 行人
  BICYCLE: 'bicycle',  // 自行车
}

const DEFAULT_LANE_WIDTH_M = 3.2; // 车道宽度

const AMBIENT_LIGHT_COLOR = 0x444444;
const DIRECTIONAL_LIGHT_COLOR = 0xffffff;

export default class Sumo3D {
  renderer;
  camera;
  scene;
  init;
  parentElement;
  controls;
  windth;
  height;
  transform;
  group;
  trafficLights;
  vClassObjects;

  constructor(parentElement, init) {
    this.parentElement = parentElement
    this.init = init
    this.vClassObjects = init.vehicles;  // 车模型对象
    this.vehicles = {};   // 正在使用的车模型集合
    this.windth = window.innerWidth
    this.height = window.innerHeight
    this.transform = getTransforms(this.init.network)
    this.group = new THREE.Group()
    this.trafficLights = new TrafficLights(init)
    this.createScene()
    this.createCamera()
    this.createRenderer()
    this.render()
    this.createControls()

    this.createLight()
    // this.createLand()
    this.createEdge()
    this.createJunctions()
    const trafficLightGroup = this.trafficLights.loadNetwork(this)
    this.trafficLights.addLogic(forceArray(init.network.net.tlLogic));
    if (init.additional && init.additional.tlLogic) {
      this.trafficLights.addLogic(forceArray(init.additional.tlLogic));
    }
    this.scene.add(trafficLightGroup)


    // const geometry = new THREE.BufferGeometry()
    // let children = this.group.children
    // for (let i = 0; i < children.length; i++) {
    //   const mesh = children[i];
    //   // geometry.merge(mesh)
    // }
    this.scene.add(this.group)
    // const [x, z] = this.transform.center()
    // this.group.position.set(-x, 0, z)
    console.log('====',this.scene);

    this.createAxis()
    setTimeout(this.render.bind(this), 300)

  }
  /** 创建地面 */
  createLand() {
    const network = this.init.network
    console.log('network', network);
    const [left, top, right, bottom] = network.net.location.convBoundary.split(',').map(Number)

    const landGeometry = new THREE.BufferGeometry()
    const points = [
      [left, top, 0],
      [right, top, 0],
      [left, bottom, 0],

      [right, bottom, 0],
      [left, bottom, 0],
      [right, top, 0]
    ]
    const positions = []
    for (let i = 0; i < points.length; i++) {
      const point = points[i];
      positions.push(...this.transform.xyToXyz(point))
    }
    landGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
    landGeometry.computeVertexNormals()
    const landMaterial = new THREE.MeshLambertMaterial({ color:0x545252, side: THREE.DoubleSide })
    const landMesh = new THREE.Mesh(landGeometry, landMaterial)
    landMesh.position.y = -5
    landMesh.name = 'Land';  // 地面
    landMesh.receiveShadow = true; // 接收光照阴影

    this.group.add(landMesh)



  }
  /** 创建车道 */
  createEdge() {
    const group = new THREE.Group()
    group.name = 'Edge'
    const network = this.init.network
    const idToType = _.keyBy(network.net.type || [], 'id');  //{id:[]}
    const idToAllowed = _.mapValues(idToType, type => indexAllowedClasses(type.allow)); // {id:{'car':true}}
    // 给车道按类型区分，公交车道，自行车道等
    const edgesByTypes = _.groupBy(network.net.edge, 'type');
    _.forEach(edgesByTypes, (edges, typeId) => {
      const allowed = idToAllowed[typeId] ? idToAllowed[typeId] : {};
      const materialFn = laneToMaterial.bind(null, idToType[typeId], allowed);
      const laneGeometries = edges //function 车道连接类型- internal：交叉口，walkingarea：人行道??
        .filter(e => e.function !== 'internal' && e.function !== 'walkingarea')
        .map(e => makeEdgeGeometry.call(this, materialFn, this.transform, e,group));

    })
    this.group.add(group)
  }
  /** 创建连接路口 */
  createJunctions() {
    const group = new THREE.Group()
    group.name = 'Junctions'
    const network = this.init.network
    const material = materials.JUNCTION;
    for (const junction of network.net.junction) {
      if (junction.type === 'internal') continue;
      const points = parseShape(junction.shape);
      // const points = parseShape(junction.shape).map(pt => this.transform.xyToXyz(pt));
      const shape = new THREE.Shape()
      shape.moveTo(points[0][0], points[0][1])
      for (let i = 1; i < points.length; i++) {
        const [x, y] = points[i];
        shape.lineTo(x, y);
      }
      shape.closePath()
      const shapeGeometry = new THREE.ShapeGeometry(shape, 40)
      const mesh = new THREE.Mesh(shapeGeometry, material)
      mesh.rotateX(-Math.PI / 2)
      group.add(mesh)
    }
    this.group.add(group)
  }
  /** 创建公交汽车站 */
  createBusStops() {
    const network = this.init.network

  }
  createScene() {
    this.scene = new THREE.Scene()
    this.scene.background = new THREE.CubeTextureLoader().setPath(CUBE_DIR).load(CUBE_FILES);
  }
  createCamera() {
    this.camera = new THREE.PerspectiveCamera(45, this.windth / this.height, 0.1, 10000)
    this.camera.up.y = 1
    // this.camera.position.set(0, 1000, 3000)
    this.camera.lookAt(this.scene.position)
    const [centerX, initY, centerZ] = this.getPosition()
    this.camera.position.set(centerX, initY, centerZ);
  }

  createRenderer() {
    this.renderer = new THREE.WebGLRenderer()
    this.renderer.setSize(this.windth, this.height)
    this.renderer.shadowMap.enabled = true
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap


    this.parentElement.appendChild(this.renderer.domElement)
  }
  /** 创建光照 */
  createLight() {
    const ambientLight = new THREE.AmbientLight(AMBIENT_LIGHT_COLOR)
    this.scene.add(ambientLight)
    const [x, z] = this.transform.center()
    const obj3d = new THREE.Object3D()
    obj3d.position.set(x, 100, -z)
    const directionalLight = new THREE.DirectionalLight(DIRECTIONAL_LIGHT_COLOR, 1)
    const [centerX, initY, centerZ] = this.getPosition()
    directionalLight.position.set(centerX, initY, centerZ)
    directionalLight.target = obj3d

    this.scene.add(obj3d)
    this.scene.add(directionalLight)

    const directionalLight2 = new THREE.DirectionalLight(DIRECTIONAL_LIGHT_COLOR, 1)
    directionalLight2.position.set(centerX, -initY, centerZ)
    this.scene.add(directionalLight2)
  }

  createControls() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement); //创建控件对象
    this.controls.addEventListener("change", this.render.bind(this)); //监听鼠标、键盘事件
  }
  /** 创建辅助坐标系 */
  createAxis() {
    const axis = new THREE.AxesHelper(1000)
    axis.add(this.group)
    this.scene.add(axis)
  }
  render() {
    this.renderer.render(this.scene, this.camera)
  }
  getPosition() {
    const init = this.init
    let [centerX, centerZ] = this.transform.xyToXz(this.transform.center());
    let initZoom = 200;
    if (init.settings && init.settings.viewsettings.viewport) {
      const { x, y, zoom } = init.settings.viewsettings.viewport;
      console.log('Focusing on ', x, y);
      [centerX, centerZ] = this.transform.xyToXz([Number(x), Number(y)]);
      if (zoom) initZoom = Number(zoom);
    }

    const maxInitY = 0.5 / FOG_RATE; // 除此之外，你什么也看不见。
    // https://stackoverflow.com/a/31777283/388951.
    const initY = Math.min(
      this.transform.width() * 100 / initZoom / (2 * Math.tan(this.camera.fov * Math.PI / 360)),
      maxInitY,
    );
    return [centerX, initY, centerZ]
  }
  /** 创建车模型 */
  createVehicleObject(vehicleId, info) {
    const vehicle = Vehicle.fromInfo(this.vClassObjects, vehicleId, info);
    if (vehicle) {
      this.vehicles[vehicleId] = vehicle;
      this.updateVehicleMesh(vehicle);
      this.scene.add(vehicle.mesh);
    }
  }
  /** 更新车模型，使其移动 */
  updateVehicleMesh(vehicle) {
    const v = vehicle.vehicleInfo;
    const obj = vehicle.mesh;
    const [x, y, z] = this.transform.xyToXyz([v.x, v.y]);
    const angle = THREE.Math.degToRad(180 - v.angle);
    obj.position.set(x,y,z);
    obj.rotation.set(0, angle, 0);
  }
  /** 根据车当前的数据更新其位置 */
  updateVehicleObject(vehicleId, update) {
    const vehicle = this.vehicles[vehicleId];
    if (vehicle) {
      _.extend(vehicle.vehicleInfo, update);
      this.updateVehicleMesh(vehicle);
    }
  }
  /** 当车驶离场景后，移除该车辆 */
  removeVehicleObject(vehicleId){
    const vehicle = this.vehicles[vehicleId];
    if (vehicle) {
      this.scene.remove(vehicle.mesh);
     
      delete this.vehicles[vehicleId];
    }
  }
  /** 更新信号灯 */
  updateLightObject(lightId, update) {
    const {programID, phase} = update;
    if (programID !== undefined) {
      this.trafficLights.setLightProgram(lightId, programID);
    }
    if (phase !== undefined) {
      this.trafficLights.setPhase(lightId, phase);
    }
  }

}

function laneToMaterial(type, allowed, edge, lane) {
  if (edge.function === 'crossing') {  // 车道类型
    return materials.CROSSING;
  } else if (lane.allow && lane.allow.includes('pedestrian')) { // 车辆允许通行类型（allow）
    return materials.WALKWAY;
  } else if (lane.allow && lane.allow.includes('bicycle')) {
    return materials.CYCLEWAY;
  } else if (type && type.allow === 'pedestrian') {
    return materials.WALKWAY;
  } else if (type && type.allow === 'bicycle') {
    return materials.CYCLEWAY;
  } else if (isRailway(allowed)) {
    return materials.RAILWAY;
  } else {
    return materials.ROAD;
  }
}


function isRailway(allowed) {
  return (
    (allowed[VehicleClass.RAIL_ELECTRIC] ||
      allowed[VehicleClass.RAIL] ||
      allowed[VehicleClass.RAIL_URBAN] ||
      allowed[VehicleClass.TRAM]) &&
    !allowed[VehicleClass.PASSENGER]
  );
}
function makeEdgeGeometry(materialFn, transform, edge,group) {
  forceArray(edge.lane).map(lane => {
    const cords = parseShape(lane.shape)
    const width = lane.width ? Number(lane.width) : DEFAULT_LANE_WIDTH_M;
    const simplicialComplex = extrudePolyline({
      thickness: width, // 线条粗细
      cap: 'square', // 帽子类型，可以是'butt'或'square'- 默认'butt'
      join: 'bevel', // 连接类型，可以是'miter'或'bevel'- 默认“斜接”
      miterLimit: 10, // 斜接变成斜角之前的极限；默认 10
    }).build(cords);

    const geometry = new THREE.BufferGeometry()
    geometry.name = 'edge';  // 车道
    geometry.receiveShadow = true; // 接收光照阴影
    const positions = []  // 顶点数据
    const indexs = []     // 顶点索引

    simplicialComplex.positions.map((position, index) => {
      positions.push(...transform.xyToXyz(position))
    })
    simplicialComplex.cells.map(cell => {
      indexs.push(...cell)
    })

    geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
    geometry.index = new THREE.BufferAttribute(new Uint32Array(indexs), 1)
    const uv = materials.addUVMappingToGeometryWithPolyline(geometry,cords,transform,width)
    geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uv), 2))
    

    geometry.computeVertexNormals()

    const material = materialFn(edge, lane); // 不同车道使用不同的材质
    // console.log('material',geometry);
 

    const mesh = new THREE.Mesh(geometry, material)

    // this.scene.add(mesh)
    group.add(mesh)

  })
}
function indexAllowedClasses(allow) {
  return makeLookup((allow || '').split(' '));
}